package com.htfyun.factorytest.agingtest;

import android.content.Context;
import android.os.Environment;
import android.text.format.Formatter;

import com.htfyun.factorytest.common.FTUtils;
import com.htfyun.factorytest.utils.DebugLog;
import com.htfyun.factorytest.utils.FileReadWrite;
import com.htfyun.factorytest.utils.TimeStringUtils;
import com.htfyun.factorytest.utils.UsefulUtils;
import com.htfyun.factorytest.utils.executor.AppExecutors;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

public class TestEmmcController {

    public final static int ONE_M = 1024 * 1024;//1M = 1024 * 1024 bytes
    public final static long ONE_G = 1024L * ONE_M;
    public final static long HALF_G = ONE_G / 2;
    public final static long _256_M = HALF_G / 2;
    public final static long _128_M = _256_M / 2;

    public final static int RW_BUFFER_SIZE = /*UsefulUtils.isLowRamDevice() ? 1024 : */1024 * 4;


    private final static String TEST_DIR = FTUtils.getFTStorageDirectory() + File.separator + "emmc";

    private final static String DIR_PATH_emmc_1g = TEST_DIR + File.separator + "emmc_1g";
    private final static String DIR_PATH_emmc_512m = TEST_DIR + File.separator + "emmc_512m";
    private final static String DIR_PATH_emmc_256m = TEST_DIR + File.separator + "emmc_256m";
    private final static String DIR_PATH_emmc_128m = TEST_DIR + File.separator + "emmc_128m";

    private final static int MAX_BYTE = 256;

    //SD 卡剩余空间约为 26G.
    //分别写 1G, 500M, 256M, 128M 文件

    private Map<EmmcInfo, Integer> emmcCountMap = new HashMap<>();

    private static TestEmmcController instance;

    private Context context;

    private long marginSpace = 0L;

    private TestEmmcController() {
        initEmmcCountMap();
    }

    public static TestEmmcController getInstance() {
        if (instance == null) {
            synchronized (TestEmmcController.class) {
                if (instance == null) {
                    instance = new TestEmmcController();
                }
            }
        }
        return instance;
    }

    private void initEmmcCountMap() {

        emmcCountMap.clear();

        // 1G 余量
        long SDFreeSpace = Environment.getExternalStorageDirectory().getFreeSpace() - ONE_G - marginSpace;

        for (EmmcInfo info : EmmcInfo.values()) {
            long space = (long) (SDFreeSpace * info.getRatio());

            int count = (int) (align_FLOOR_2_POWER(space, info.getSize()) / info.getSize());

            DebugLog.e("info = " + info + ", count = " + count);

            emmcCountMap.put(info, count);

        }
    }


    public void setContext(Context ctx) {
        context = ctx;

        if (context == null) {
            return;
        }

        long SDFreeSpace = Environment.getExternalStorageDirectory().getFreeSpace() - ONE_G - marginSpace;
        DebugLog.e("test emmc free Space = " + Formatter.formatFileSize(ctx, SDFreeSpace));
    }

    public void setMarginSpace(long space) {
        if (space < 0) {
            return;
        }
        marginSpace = space;
        initEmmcCountMap();
    }

    private volatile boolean isStarted = false;
    private final Object startLock = new Object();

    public void startTest() {

        synchronized (startLock) {
            if (isStarted) {
                return;
            }
            isStarted = true;
        }

        for (EmmcInfo info : EmmcInfo.values()) {

            Integer count = emmcCountMap.get(info);

            if (count == null) {
                count = 0;
            }

            File dir = new File(info.getDir());
            if (dir.isDirectory()) {
                File[] files = dir.listFiles();
                if (files.length > 0) {
                    for (File file : files) {
                        if (!file.isFile()) {
                            continue;
                        }

                        /*
                        if (file.length() > info.getSize()
                            || file.length() < ONE_M
                            || !isMyFile(file)) {

                            file.delete();
                            continue;
                        }

                        count++;
                        */
                        // 20190619,hsl:delete all file before test!!
                        file.delete();
                    }
                }
            }

            System.gc();
            System.gc();
            System.gc();
            System.gc();

            AppExecutors.runOnIoThread(new TestRunnable(info, count));
        }

    }

    public void stopTest() {

        synchronized (startLock) {
            isStarted = false;
        }
    }

    public void deleteTestFiles() {
        FileReadWrite.deleteFile(TEST_DIR);
        System.gc();
        System.gc();
        System.gc();
        System.gc();
        System.gc();
    }

    private boolean isMyFile(File file) {
        InputStream read = null;
        try {

            read = new FileInputStream(file);

            byte[] buf = new byte[ONE_M];

            int length = read.read(buf);

            if (length != buf.length) {
                return false;
            }

            for (int i = 0; i < length; ) {
                for (int j = 0; j < 256; j++) {
                    if (j != (0xff & buf[i++])) {
                        return false;
                    }
                }
            }

        } catch (Exception e) {
            DebugLog.e("Exception = " + e.getMessage());
            e.printStackTrace();
        } finally {

            if (read != null) {
                try {
                    read.close();
                    read = null;
                } catch (Exception e1) {
                    DebugLog.e("Exception = " + e1.getMessage());
                    e1.printStackTrace();
                }
            }
        }

        return true;
    }

    private long align_CELING_2_POWER(long x, long a) {
        return (((x) + ((a) - 1)) & (-(a)));
    }

    private long align_FLOOR_2_POWER(long x, long a) {
        return ((x) & (-(a)));
    }

    public enum EmmcInfo {
        one_g(DIR_PATH_emmc_1g, ONE_G, 0.9f),
//        half_g(DIR_PATH_emmc_512m, HALF_G, 0.25f),
//        _256m(DIR_PATH_emmc_256m, _256_M, 0.25f),
//        _128m(DIR_PATH_emmc_128m, _128_M, 0.25f),
        ;
        private final String dir;
        private final long size;
        private final float ratio;

        EmmcInfo(String dir, long size, float ratio) {
            this.dir = dir;
            this.size = size;
            this.ratio = ratio;
        }

        public String getDir() {
            return dir;
        }

        public long getSize() {
            return size;
        }


        public float getRatio() {
            return ratio;
        }
    }

    private List<OnWROverListener> onWROverListenerList = new CopyOnWriteArrayList<>();

    public interface OnWROverListener {
        void onWriteOverListener(EmmcInfo info, long time_ms);

        void onReadOverListener(EmmcInfo info, long time_ms);

        void onTestFailed(EmmcInfo info, String filePath, long offset, byte shouldValue, byte realValue);
    }

    public void addOnWROverListener(OnWROverListener listener) {
        if (!onWROverListenerList.contains(listener)) {
            onWROverListenerList.add(listener);
        }
    }

    public void removeOnWROverListener(OnWROverListener listener) {
        onWROverListenerList.remove(listener);
    }

    private void onWriteOverAction(EmmcInfo info, long time_ms) {
        for (OnWROverListener listener : onWROverListenerList) {
            listener.onWriteOverListener(info, time_ms);
        }
    }

    private void onReadOverAction(EmmcInfo info, long time_ms) {
        for (OnWROverListener listener : onWROverListenerList) {
            listener.onReadOverListener(info, time_ms);
        }
    }

    private void onTestFailed(EmmcInfo info, String filePath, long offset, byte shouldValue, byte realValue) {
        for (OnWROverListener listener : onWROverListenerList) {
            listener.onTestFailed(info, filePath, offset, shouldValue, realValue);
        }
    }

    private final class TestRunnable implements Runnable {

        private final EmmcInfo emmcInfo;
        private final int count;

        public TestRunnable(EmmcInfo info, int count) {
            this.emmcInfo = info;
            this.count = count;
        }

        @Override
        public void run() {

            DebugLog.e("start test emmcInfo = " + emmcInfo + ", count = " + count + ", thread = " + Thread.currentThread().getId());

            checkDir(emmcInfo.getDir());

            // 20190619: use independent buffer every thread.
            byte[] wrBuffer = new byte[RW_BUFFER_SIZE];

            testWrite(wrBuffer);
            testRead(wrBuffer);

            // 20190619: free the buffer after write.
            wrBuffer = null;
        }

        private void testWrite(byte[] wrBuffer) {

            long start = System.currentTimeMillis();

            for (int i = 0; i < count; i++) {

                final String filePath = emmcInfo.getDir() + File.separator + (i + ".bin");
                if (checkFile(filePath)) {
                    writeData(new File(filePath), emmcInfo.getSize(), wrBuffer);
                } else {
                    DebugLog.e("testWrite: emmcInfo = " + emmcInfo + ", Create File " + filePath + " Failed!!");
                }
                synchronized (startLock) {
                    if (!isStarted) {
                        break;
                    }
                }
            }

            long end = System.currentTimeMillis();

            if (context != null) {
                DebugLog.e("write time: emmcInfo = " + emmcInfo + ", write used time = " + TimeStringUtils.makeTimeString((end - start)));
            }

            onWriteOverAction(emmcInfo, end - start);

        }

        private void testRead(byte[] wrBuffer) {

            long start = System.currentTimeMillis();

            for (int i = 0; i < count; i++) {

                String filePath = emmcInfo.getDir() + File.separator + (i + ".bin");
                File file = new File(filePath);
                //checkFile(filePath);
                if (file.exists()) {
                    readData(file, wrBuffer);
                } else {
                    DebugLog.e("testRead file not exist");
                    // 20190619: if Not exist,something wrong??
//                    onTestFailed(emmcInfo, filePath, -1, (byte)0xef, 0);
                }
                synchronized (startLock) {
                    if (!isStarted) {
                        break;
                    }
                }
            }

            long end = System.currentTimeMillis();

            if (context != null) {
                DebugLog.e("read time: emmcInfo = " + emmcInfo + ", read used time = " + TimeStringUtils.makeTimeString((end - start)));
            }

            onReadOverAction(emmcInfo, end - start);

        }


        private void readData(File file, byte[] wrBuffer) {

            InputStream read = null;
            try {

                read = new FileInputStream(file);
                long offset = 0;

                int length = 0;
                //int cycleCount = 0;
                byte[] readBuffer = wrBuffer; // new byte[ONE_M];

                while ((length = read.read(readBuffer)) != -1) {
                    if (length != readBuffer.length) {
                        DebugLog.e("read: error?, emmcInfo = " + emmcInfo
                                + ", offset=" + offset
                                + ", buffer length=" + readBuffer.length + ",read length=" + length);
                    }
                    if (length > 0) {
                        for (int i = 0; i < length; i++) {
                            if ((byte) (offset & 0xff) != (readBuffer[i])) {
                                DebugLog.e("read: test err, emmcInfo = " + emmcInfo
                                        + ", j = 0x" + Integer.toHexString((int) (0xff & offset))
                                        + ", buf[" + i + "] = 0x" + Integer.toHexString(readBuffer[i]));

                                onTestFailed(emmcInfo, file.getPath(), offset,
                                        (byte) (0xff & offset), readBuffer[i]);
                            }
                            offset++;
                        }
                    }

                    //cycleCount++;

                    synchronized (startLock) {
                        if (!isStarted) {
                            break;
                        }
                    }
                }

            } catch (Exception e) {
                DebugLog.e("Exception = " + e.getMessage());
                e.printStackTrace();

            } finally {

                for (int i = 0; i < 10; i++) {
                    System.gc();
                }

                if (read != null) {
                    try {
                        read.close();
                        read = null;
                    } catch (Exception e1) {
                        DebugLog.e("Exception = " + e1.getMessage());
                        e1.printStackTrace();
                    }

                }

            }
        }

        private void writeData(File file, long totalSize, byte[] wrBuffer) {

            byte[] writeOneBlockBuffer = wrBuffer; //new byte[ONE_M];

            for (int i = 0; i < writeOneBlockBuffer.length; ) {
                for (int j = 0; j < MAX_BYTE; j++) {
                    writeOneBlockBuffer[i++] = (byte) j;
                }
            }

            OutputStream writer = null;

            try {

                writer = new FileOutputStream(file, true);

                long size = file.length();

                while (size < totalSize) {

                    writer.write(writeOneBlockBuffer);

                    writer.flush();

                    //size = file.length();
                    size += writeOneBlockBuffer.length;

                    synchronized (startLock) {
                        if (!isStarted) {
                            break;
                        }
                    }
                }

            } catch (Exception e) {
                DebugLog.e("Exception = " + e.getMessage());
                e.printStackTrace();

            } finally {

                for (int i = 0; i < 10; i++) {
                    System.gc();
                }
                if (writer != null) {
                    try {
                        writer.close();
                        writer = null;
                    } catch (Exception e1) {
                        DebugLog.e("Exception = " + e1.getMessage());
                        e1.printStackTrace();
                    }

                }
            }
        }

        private void checkDir(String dirPath) {
            File file = new File(dirPath);
            if (!file.exists()) {
                try {
                    file.mkdirs();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private boolean checkFile(String filePath) {
            File file = new File(filePath);

            if (!file.exists()) {
                try {
                    file.createNewFile();
                    return true;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            return false;
        }

    }
}
